/*
C++20 日志实现  需要 g++13以上 或者 vs2022以上
参考链接：双笙子佯谬
https://www.bilibili.com/video/BV1t94y1r72E/?spm_id_from=333.337.search-card.all.click&vd_source=599963530e0401186f03150344074c1a
*/

#ifndef __LOG_H__
#define __LOG_H__

#include <format>
#include <source_location>
#include <chrono>
#include <iostream>
#include <fstream>
#include <cstdint>

#define MINILOG_FOREACH_LOG_LEVEL(f)\
	f(trace)				\
	f(debug)				\
	f(info)					\
	f(critical)				\
	f(warning)				\
	f(error)				\
	f(fatal)

enum class log_level : std::uint8_t
{
#define XX(name) name,
	MINILOG_FOREACH_LOG_LEVEL(XX)
#undef XX
};


namespace details { //添加命名空间，减少内部接口的暴露

#if defined(__linux__) || defined(__APPLE__)
//定义 ANSI控制码 二维数组，Linux特有，给不同日志级别添加不同的颜色
inline constexpr char k_level_ansi_colors[(std::uint8_t)log_level::fatal + 1][8] = {
	"\E[37m",
	"\E[35m",
	"\E[32m",
	"\E[34m",
	"\E[33m",
	"\E[31m",
	"\E[31;1m",
};

inline constexpr char k_reset_ansi_color[4] = "\E[m";
// #define MINILOG_IF_HAS_ANSI_COLORS(x) x
#else
// #define MINILOG_IF_HAS_ANSI_COLORS(x)
inline constexpr char k_level_ansi_colors[(std::uint8_t)log_level::fatal + 1][8] = {
	"\033[37m",
	"\033[35m",
	"\033[32m",
	"\033[34m",
	"\033[33m",
	"\033[31;1m",
	"\033[31m",
};
inline constexpr char k_reset_ansi_color[5] = "\033[0m";
#endif

	//将日志级别转换为字符串
	inline std::string log_level_name(log_level lev)
	{
		switch (lev)
		{
#define XX(name) case log_level::name: return #name;
			MINILOG_FOREACH_LOG_LEVEL(XX)
#undef XX
		default:
			break;
		}

		return "unknown";
	}

	//将字符串转换为日志级别
	inline log_level log_level_frame_name(std::string lev)
	{
#define XX(name) if(lev == #name) return log_level::name;
			MINILOG_FOREACH_LOG_LEVEL(XX)
#undef XX
			return log_level::info;
	}

	//std::constructible_from<T, U>概念检查是否可以使用给定的一组参数类型U初始化类型为T的变量
	template <class T>
	struct with_source_location {
	private:
		T inner;
		std::source_location loc;

	public:
		//consteval 只能编译期调用
		template <class U> requires std::constructible_from<T, U>  //T为std::format_string类型，用参数U创建一个std::format_string类型的变量
		consteval with_source_location(U&& inner, std::source_location loc = std::source_location::current())
			: inner(std::forward<U>(inner)), loc(std::move(loc)) {}
		constexpr T const& format() const { return inner; }
		constexpr std::source_location const& location() const { return loc; }
	};

	//定义并初始化日志级别
	// char *getenv(const char *name) 请求环境变量name的值。如果该环境变量不存在，则返回 NULL
	inline log_level g_max_level = []()->log_level {
		if (auto lev = std::getenv("MY_LOG_LEVEL"))
		{
			return log_level_frame_name(lev);
		}
		return log_level::info;
	}();
	
	//定义日志输出路径
	inline std::ofstream g_log_file = []() -> std::ofstream {
		if (auto path = std::getenv("MINILOG_FILE")) {
			return std::ofstream(path, std::ios::app);
		}
		return std::ofstream();
		} ();


	inline void output_log(log_level lev, std::string msg, std::source_location const& loc) 
	{
		
		std::chrono::zoned_time<std::chrono::system_clock::duration> now{ std::chrono::current_zone(), std::chrono::system_clock::now() };
		msg = std::format("{} {}:{} [{}] {}", now, loc.file_name(), loc.line(), details::log_level_name(lev), msg);
		if (g_log_file) {


			g_log_file << msg + '\n';
		}
		if (lev >= g_max_level) {
			// std::cout << MINILOG_IF_HAS_ANSI_COLORS(k_level_ansi_colors[(std::uint8_t)lev] + )
			// 	msg MINILOG_IF_HAS_ANSI_COLORS(+k_reset_ansi_color) + '\n';
			//输出一行日志只用了一个 “ << ” ，所以是线程安全的 
			std::cout << k_level_ansi_colors[(std::uint8_t)lev] + msg + k_reset_ansi_color + '\n';
		}
	}


} //namespace details


//设置日志级别
inline void set_log_level(log_level lev)
{
	details::g_max_level = lev;
}

//设置日志输出路径
inline void set_log_file(std::string path) {
	details::g_log_file = std::ofstream(path, std::ios::app);
}




//打印日志  在这里构造with_source_location主要是为了在代码调用处调用source_location以获得代码行号等信息
template<typename... Args>
void generic_log(log_level lev, details::with_source_location<const std::format_string<Args...>> fmt, Args&&... args)//照抄std::format的参数
{
	auto const& loc = fmt.location();                                   //照抄std::format的实现
	auto msg = std::vformat(fmt.format().get(), std::make_format_args(args...));
	details::output_log(lev, std::move(msg), loc);
}

//编写打印各种日志级别的函数
#define XX(name)																				\
template<typename... Args>																		\
void log_##name(details::with_source_location<const std::format_string<Args...>> fmt, Args&&... args)    \
{																								\
	generic_log(log_level::name, std::move(fmt), std::forward<Args>(args)...);					\
}
MINILOG_FOREACH_LOG_LEVEL(XX)
#undef XX

//只打印变量名和变量值的简化版本   "value" " = {} " 会自动连到一起  "value = {} "
#define LOG_PRINT(value) log_debug(#value " = {} ", value);

#endif // !__LOG_H__
